#include "te_Renderer.h"

using namespace irr;

using namespace core;
using namespace gui;
using namespace video;
using namespace scene;
using namespace io;

const char *irrDriverTypes[] = { "software", "burning", "d3d8", "d3d9", "opengl", NULL };

CVar r_fullscreen("r_fullscreen", "0", CVAR_BOOL|CVAR_ARCHIVE|CVAR_RENDERER|CVAR_VIDRESTART, "Fullscreen mode");
CVar r_antialias("r_antialias", "0", CVAR_BOOL|CVAR_ARCHIVE|CVAR_RENDERER|CVAR_VIDRESTART, "Antialiasing");
CVar r_height("r_height", "480", CVAR_INTEGER|CVAR_ARCHIVE|CVAR_RENDERER|CVAR_VIDRESTART, "Screen resolution (H)", 480, 5192);
CVar r_width("r_width", "640", CVAR_INTEGER|CVAR_ARCHIVE|CVAR_RENDERER|CVAR_VIDRESTART, "Screen resolution (W)", 640, 5192);
CVar r_colorbits("r_colorbits", "16", CVAR_INTEGER|CVAR_ARCHIVE|CVAR_RENDERER|CVAR_VIDRESTART, "Color bits (does not apply in windowed mode", 16, 32);
CVar r_stereoscopic("r_stereoscopic", "0", CVAR_BOOL|CVAR_ARCHIVE|CVAR_RENDERER|CVAR_VIDRESTART, "Use Stereoscopic 3D rendering?");
CVar r_vsync("r_vsync", "0", CVAR_BOOL|CVAR_ARCHIVE|CVAR_RENDERER|CVAR_VIDRESTART, "Vertical sync");
CVar r_renderer("r_renderer", "software", CVAR_ENUM|CVAR_ARCHIVE|CVAR_RENDERER|CVAR_VIDRESTART, "Rendering mode", irrDriverTypes);

Renderer *Renderer::instance = NULL;

// Renderer class constructor / destructor
Renderer::Renderer()
{
	Init();
}

bool Renderer::Run()
{
	return irrDevice->run();
}

Renderer::~Renderer()
{
	// Need to loop through all the bank resources and drop them
	int i;

	// IImages
	for(i = 0; i < bankImages.Num(); i++)
	{
		if(bankImages[i])
		{
			bankImages[i]->drop();
		}
	}

	// ITextures
	for(i = 0; i < bankTextures.Num(); i++)
	{
		if(bankTextures[i])
		{
			bankTextures[i]->drop();
		}
	}

	// IGUIFonts
	for(i = 0; i < bankFonts.Num(); i++)
	{
		if(bankFonts[i])
		{
			bankFonts[i]->drop();
		}
	}

	irrDevice->drop();
	// The EventManager was brought into this world, and we're taking it out of it!
	EventManager::DestroySingleton();
}

// Renderer class singleton management
Renderer *Renderer::GetSingleton()
{
	if(!Renderer::instance)
	{
		Renderer::instance = new Renderer();
	}
	return Renderer::instance;
}

void Renderer::DestroySingleton()
{
	delete Renderer::instance;
}

void Renderer::Init()
{
	SIrrlichtCreationParameters params;
	params.AntiAlias = r_antialias.GetBool();
	params.Bits = r_colorbits.GetInt();
	params.DriverType = (E_DRIVER_TYPE)(r_renderer.GetInt()+1);
	params.Fullscreen = r_fullscreen.GetBool();
	params.Stereobuffer = r_stereoscopic.GetBool();
	params.Vsync = r_vsync.GetBool();
	params.WindowSize = dimension2d<u32>(r_width.GetInt(), r_height.GetInt());
	params.EventReceiver = EventManager::GetSingleton();		// The GameEngine handles all of the input
	irrDevice = createDeviceEx( params );


	if (!irrDevice)
		return;			// TODO: Com_Error

	irrDevice->setWindowCaption(L"BaseEngine");

	irrDriver = irrDevice->getVideoDriver();
	irrScene = irrDevice->getSceneManager();
	irrGUI = irrDevice->getGUIEnvironment();

	QueryDriver();

	//irrGUI->addStaticText(L"Hello World! This is the Irrlicht Software renderer!",
	//	rect<s32>(10,10,260,22), true);
}

void Renderer::QueryDriver()
{
	// DEBUG: these just print, but in the future, these will have some settings for enhanced driver support.
	if(!irrDriver->queryFeature(video::EVDF_ALPHA_TO_COVERAGE))
	{
		Console::Print("Driver does not support Alpha to Coverage\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
	{
		Console::Print("Driver does not support ARB Fragment shaders v1.0\n");
	}
	if(!strcmp(r_renderer.GetString(), "opengl") && !irrDriver->queryFeature(video::EVDF_ARB_GLSL))
	{
		Console::Print("Driver does not support GLSL shaders\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
	{
		Console::Print("Driver does not support ARB Vertex shaders v1.0\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_BILINEAR_FILTER))
	{
		Console::Print("Driver does not support Bilinear Filtering\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_COLOR_MASK))
	{
		Console::Print("Driver does not support Color Masks\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_FRAMEBUFFER_OBJECT))
	{
		Console::Print("Driver does not support Framebuffer Objects\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_GEOMETRY_SHADER))
	{
		Console::Print("Driver does not support geometry shaders\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_HARDWARE_TL))
	{
		Console::Print("Driver does not support hardware transform and lighting\n");
	}
	if(!strcmp(r_renderer.GetString(), "d3d9") && !irrDriver->queryFeature(video::EVDF_HLSL))
	{
		Console::Print("Driver does not support HLSL\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_MIP_MAP))
	{
		Console::Print("Driver does not support mipmaps\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_MIP_MAP_AUTO_UPDATE))
	{
		Console::Print("Driver does not update mipmaps automatically\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_MRT_BLEND))
	{
		Console::Print("Driver does not support MRT blend settings\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_MRT_BLEND_FUNC))
	{
		Console::Print("Driver does not support MRT blend functions\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_MRT_COLOR_MASK))
	{
		Console::Print("Driver does not support MRT color masks\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_MULTIPLE_RENDER_TARGETS))
	{
		Console::Print("Driver does not support multiple render targets\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_MULTITEXTURE))
	{
		Console::Print("Driver does not support multitexturing\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_PIXEL_SHADER_1_1))
	{
		Console::Print("Driver does not support Pixel Shader 1.1\n");
	}
	else if(!irrDriver->queryFeature(video::EVDF_PIXEL_SHADER_1_2))
	{
		Console::Print("Driver does not support Pixel Shader 1.2\n");
	}
	else if(!irrDriver->queryFeature(video::EVDF_PIXEL_SHADER_1_3))
	{
		Console::Print("Driver does not support Pixel Shader 1.3\n");
	}
	else if(!irrDriver->queryFeature(video::EVDF_PIXEL_SHADER_1_4))
	{
		Console::Print("Driver does not support Pixel Shader 1.4\n");
	}
	else if(!irrDriver->queryFeature(video::EVDF_PIXEL_SHADER_2_0))
	{
		Console::Print("Driver does not support Pixel Shader 2.0\n");
	}
	else if(!irrDriver->queryFeature(video::EVDF_PIXEL_SHADER_3_0))
	{
		Console::Print("Driver does not support Pixel Shader 3.0\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_RENDER_TO_TARGET))
	{
		Console::Print("Driver does not support render-to-target\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_STENCIL_BUFFER))
	{
		Console::Print("Stencil buffer not active (or driver does not support it)\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_TEXTURE_NPOT))
	{
		Console::Print("Driver does not support NPOT textures\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_TEXTURE_NSQUARE))
	{
		Console::Print("Driver does not support NSQUARE textures\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_VERTEX_BUFFER_OBJECT))
	{
		Console::Print("Driver does not support VBOs\n");
	}
	if(!irrDriver->queryFeature(video::EVDF_VERTEX_SHADER_1_1))
	{
		Console::Print("Driver does not support Vertex Shader 1.1\n");
	}
	else if(!irrDriver->queryFeature(video::EVDF_VERTEX_SHADER_2_0))
	{
		Console::Print("Driver does not support Vertex Shader 2.0\n");
	}
	else if(!irrDriver->queryFeature(video::EVDF_VERTEX_SHADER_3_0))
	{
		Console::Print("Driver does not support Vertex Shader 3.0\n");
	}
}

// TODO: RE_RegisterShader-like image registration
IImage *Renderer::GetImageResource(const char *fileName)
{
	// First, check and make sure the texture resource exists
	if(!FS->FileExists(fileName))
	{
		Console::Print(Format("WARNING: Attempted to access texture resource with filename $*\n") << fileName);
		return NULL;
	}

	og::String strippedFile(fileName);

	// Next, check and make sure that it isn't already in the image resource bank
	if(int imgBankIndex = bankImages.Find(strippedFile.c_str()) != -1)
	{
		// Found it, just use that image
		return bankImages[imgBankIndex];
	}

	File *imageFileFromFS = FS->OpenRead(fileName);
	
	byte *contents = new byte[imageFileFromFS->Size()];
	FS->LoadFile(fileName, (byte **)&contents);

	if(!FS->StoreFile(fileName, (byte *)contents, imageFileFromFS->Size()))
	{
		Console::Print(Format("WARNING: Could not load texture resource $*\n") << fileName);
		return NULL;
	}

	io::IFileSystem* fs = irrDevice->getFileSystem();
	if(!fs)
	{
		Console::Print(Format("WARNING: Renderer::GetImageResource(): NULL fs on $*\n") << fileName);
		return NULL;
	}

	// Give it the proper extension so that Irrlicht knows how to parse it
	string<c8> imgFileLoc = fileName;
	IReadFile *imgFile = fs->createMemoryReadFile((void *)contents, imageFileFromFS->Size(), "dummy.jpg", true);		// TODO: something other than jpg support
	if(!imgFile)
	{
		Console::Print(Format("WARNING: Renderer::GetImageResource(): fs->createMemoryReadFile failed on $*\n") << fileName);
		return NULL;
	}

	IImage *img = irrDriver->createImageFromFile(imgFile);
	if(!img)
	{
		Console::Print(Format("WARNING: Renderer::GetImageResource(): irrDriver->createImageFromFile failed on $*\n") << fileName);
		return NULL;
	}

	bankImages[strippedFile.c_str()] = img;

	return img;
}

IGUIFont *Renderer::GetFontResource(const char *fontFileName)
{
	if(!FS->FileExists(fontFileName))
	{
		Console::Print(Format("WARNING: Attempted to access font resource with filename $*\n") << fontFileName);
		return NULL;
	}

	og::String strippedFile(fontFileName);

	// Next, check and make sure that it isn't already in the font resource bank
	if(int fontBankIndex = bankFonts.Find(strippedFile.c_str()) != -1)
	{
		// Found it, just use that image
		return bankFonts[fontBankIndex];
	}

	File *fontFileFromFS = FS->OpenRead(fontFileName);
	
	byte *contents = new byte[fontFileFromFS->Size()];
	FS->LoadFile(fontFileName, (byte **)&contents);

	if(!FS->StoreFile(fontFileName, (byte *)contents, fontFileFromFS->Size()))
	{
		Console::Print(Format("WARNING: Could not load font resource $*\n") << fontFileName);
		return NULL;
	}

	io::IFileSystem* fs = irrDevice->getFileSystem();
	if(!fs)
	{
		Console::Print(Format("WARNING: Renderer::GetFontResource(): NULL fs on $*\n") << fontFileName);
		return NULL;
	}

	// Give it the proper extension so that Irrlicht knows how to parse it
	string<c8> fontFileLoc = fontFileName;
	//io::CMemoryFile *fontFile = new CMemoryFile(contents, fontFileFromFS->Size(), fontFileName, true);
	CMemoryFile *fontFile = fs->createIMemoryReadFile(contents, fontFileFromFS->Size(), fontFileName);
	if(!fontFile)
	{
		Console::Print(Format("WARNING: Renderer::GetFontResource(): fs->createMemoryReadFile failed on $*\n") << fontFileName);
		return NULL;
	}

	IGUIFont *font = irrGUI->getFontFromMemory(fontFile);
	if(!font)
	{
		Console::Print(Format("WARNING: Renderer::GetFontResource(): irrDriver->createImageFromFile failed on $*\n") << fontFileName);
		return NULL;
	}

	bankFonts[strippedFile.c_str()] = font;

	return font;
	return NULL;
}